#!/bin/bash
#
# Copyright (C) 2017 Julien Desfossez <jdesfossez@efficios.com>
#
# SPDX-License-Identifier: LGPL-2.1-only

TEST_DESC="Rotation - User space tracing"

CURDIR=$(dirname $0)/
TESTDIR=$CURDIR/../../..
NR_USEC_WAIT=0
TESTAPP_PATH="$TESTDIR/utils/testapp"
TESTAPP_NAME="gen-ust-events"
TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME"
SESSION_NAME="stream"
EVENT_NAME="tp:tptest"

TRACE_PATH=$(mktemp -d -t tmp.test_ust_rotation_trace_path.XXXXXX)

NUM_TESTS=152

source $TESTDIR/utils/utils.sh
source $CURDIR/rotate_utils.sh

if [ ! -x "$TESTAPP_BIN" ]; then
	BAIL_OUT "No UST events binary detected."
fi

XPATH_CMD_OUTPUT="//lttng:command/lttng:output"
XPATH_PID="$XPATH_CMD_OUTPUT/lttng:domains/lttng:domain[./lttng:type = 'UST']/lttng:pids/lttng:pid/lttng:id"

function enable_channel_per_pid ()
{
	sess_name=$1
	channel_name=$2

	enable_ust_lttng_channel_ok $sess_name $channel_name --buffers-pid
}

# MUST set TESTDIR before calling those functions

function rotate_ust_test ()
{
	local_path=$1
	app_path=$2
	per_pid=$3

	start_lttng_tracing_ok $SESSION_NAME
	today=$(date +%Y%m%d)

	$TESTAPP_BIN -i 10 -w $NR_USEC_WAIT > /dev/null 2>&1
	rotate_session_ok $SESSION_NAME

	$TESTAPP_BIN -i 20 -w $NR_USEC_WAIT > /dev/null 2>&1
	stop_lttng_tracing_ok $SESSION_NAME

	# Third chunk contains no event (rotate after stop).
	rotate_session_ok $SESSION_NAME

	destroy_lttng_session_ok $SESSION_NAME

	validate_test_chunks "${local_path}" $today $app_path ust
}

function test_ust_streaming_uid ()
{
	diag "Test UST streaming with session rotation per UID"
	create_lttng_session_uri $SESSION_NAME net://localhost
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME

	rotate_ust_test "${TRACE_PATH}/${HOSTNAME}/${SESSION_NAME}*/archives" "ust/uid/*/*/" 0
}

function test_ust_local_uid ()
{
	diag "Test UST local with session rotation per UID"
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME

	rotate_ust_test "${TRACE_PATH}/archives" "ust/uid/*/*/" 0
}

function test_ust_streaming_pid ()
{
	diag "Test UST streaming with session rotation per PID"
	create_lttng_session_uri $SESSION_NAME net://localhost
	enable_channel_per_pid $SESSION_NAME "channel0"
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME "channel0"

	rotate_ust_test "${TRACE_PATH}/${HOSTNAME}/${SESSION_NAME}*/archives" "ust/pid/*/" 1
}

function test_ust_local_pid ()
{
	diag "Test UST local with session rotation per PID"
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH
	enable_channel_per_pid $SESSION_NAME "channel0"
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME "channel0"

	rotate_ust_test "${TRACE_PATH}/archives" "ust/pid/*/" 1
}

function wait_until_app_unregistration ()
{
	local pid=$1
	local mi_output
	local value=1

	mi_output=$(mktemp)

	while [ $value -ne 0 ]; do
		# Extract from lttng list tracepoint the registered apps.
		# This should give us enough guarantee since that if the app is not
		# present it is unregistered from lttng-sessiond point of view.
		LTTNG_BIN="lttng --mi xml" OUTPUT_DEST="$mi_output" list_lttng_notap -u
		if ! $MI_VALIDATE "$mi_output"; then
			return 1
		fi

		value=$("$MI_VALIDATE" "$mi_output" "$XPATH_PID" | wc -l)
		if [ $value -ne 0 ]; then
			diag "At least one app is still registred from lttng-sessiond point of view"
			sleep 1
		else
			# No app present on listing.
			break;
		fi
	done

	return 0
}

function test_ust_local_timer_uid ()
{
	diag "Test ust local with session rotation timer per-uid"
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME
	lttng_enable_rotation_timer_ok $SESSION_NAME 500ms
	start_lttng_tracing_ok $SESSION_NAME
	# We just want the app to register, no event generated
	$TESTAPP_BIN -i 0 -w 0 > /dev/null 2>&1

	rotate_timer_test "${TRACE_PATH}/archives" 0
}

function test_ust_streaming_timer_uid ()
{
	diag "Test ust remote with session rotation timer per-uid"
	create_lttng_session_uri $SESSION_NAME net://localhost
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME
	lttng_enable_rotation_timer_ok $SESSION_NAME 500ms
	start_lttng_tracing_ok $SESSION_NAME
	# We just want the app to register, no event generated
	$TESTAPP_BIN -i 0 -w 0 > /dev/null 2>&1

	rotate_timer_test "${TRACE_PATH}/${HOSTNAME}/${SESSION_NAME}*/archives" 0
}

function test_ust_local_timer_pid ()
{
	local app_pid

	diag "Test ust local with session rotation timer per-pid"
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH
	enable_channel_per_pid $SESSION_NAME "channel0"
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME "channel0"
	start_lttng_tracing_ok $SESSION_NAME

	# We just want the app to register, no event generated.
	# But we want the pid for the app since we need to validate its
	# unregistration on lttng listing.
	$TESTAPP_BIN -i 0 -w 0 > /dev/null 2>&1 &
	app_pid=$!
	wait $app_pid

	wait_until_app_unregistration $app_pid
	ok $? "App is un-registered"

	# Set the rotation after that the app is done and that it was
	# unregistered from lttng-sessiond.
	# This is necessary since the rotate_timer_test function expects the
	# second rotation archive to be empty. On slow machine it can take more
	# than 500ms for the lttng-sessiond to receive and handle an app
	# termination. This can lead to situation where the second rotation
	# archive still have the channels for the pid for the app since that
	# from the point of view of the lttng-sessiond/consumer the app is still
	# "alive" and "kicking". This is a problem only for per-pid testing.
	lttng_enable_rotation_timer_ok $SESSION_NAME 500ms

	rotate_timer_test "${TRACE_PATH}/archives" 1
}

function test_ust_streaming_timer_pid ()
{
	diag "Test ust remote with session rotation timer per-pid"
	create_lttng_session_uri $SESSION_NAME net://localhost
	enable_channel_per_pid $SESSION_NAME "channel0"
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME "channel0"
	start_lttng_tracing_ok $SESSION_NAME

	# We just want the app to register, no event generated.
	# But we want the pid for the app since we need to validate its
	# unregistration on lttng listing.
	$TESTAPP_BIN -i 0 -w 0 > /dev/null 2>&1 &
	app_pid=$!
	wait $app_pid

	wait_until_app_unregistration $app_pid
	ok $? "App is un-registered"

	# Set the rotation after that the app is done and that it was
	# unregistered from lttng-sessiond.
	# This is necessary since the rotate_timer_test function expects the
	# second rotation archive to be empty. On slow machine it can take more
	# than 500ms for the lttng-sessiond to receive and handle an app
	# termination. This can lead to situation where the second rotation
	# archive still have the channels for the pid for the app since that
	# from the point of view of the lttng-sessiond/consumer the app is still
	# "alive" and "kicking". This is a problem only for per-pid testing.
	lttng_enable_rotation_timer_ok $SESSION_NAME 500ms

	rotate_timer_test "${TRACE_PATH}/${HOSTNAME}/${SESSION_NAME}*/archives" 1
}

function test_incompatible_sessions ()
{
	diag "Check incompatible session types with rotation"

	diag "Live session with rotate timer"
	# Should not be able to enable a rotation timer with a live session
	create_lttng_session_uri $SESSION_NAME net://localhost --live
	lttng_enable_rotation_timer_fail $SESSION_NAME 500ms
	destroy_lttng_session_ok $SESSION_NAME

	diag "Snapshot session with rotate timer"
	# Should not be able to enable a rotation timer with a snapshot session
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH --snapshot
	lttng_enable_rotation_timer_fail $SESSION_NAME 500ms
	destroy_lttng_session_ok $SESSION_NAME

	diag "Live session with rotate"
	# Should not be able to rotate a live session
	create_lttng_session_uri $SESSION_NAME net://localhost --live
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME
	start_lttng_tracing_ok $SESSION_NAME
	rotate_session_fail $SESSION_NAME
	destroy_lttng_session_ok $SESSION_NAME

	diag "Snapshot session with rotate"
	# Should not be able to rotate a snapshot session
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH --snapshot
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME
	start_lttng_tracing_ok $SESSION_NAME
	rotate_session_fail $SESSION_NAME
	destroy_lttng_session_ok $SESSION_NAME
}

function produce_n_events ()
{
	local event_count=$1

	$TESTAPP_BIN -i "$event_count" -w 0 > /dev/null 2>&1
}

function test_ust_local_size_uid ()
{
	diag "Rotate uid local session every 2MiB"
	local size_threshold=$((2 * 1024 * 1024))

	diag "Test ust local with size-based session rotation per-uid"
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH
	enable_ust_lttng_channel_ok $SESSION_NAME "channel0" --buffers-uid
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME "channel0"
	lttng_enable_rotation_size_ok $SESSION_NAME $size_threshold
	start_lttng_tracing_ok $SESSION_NAME

	# Cutoff at 100 times the expected size
	trace_until_n_archives produce_n_events "$TRACE_PATH" 5 $((5 * 100 * size_threshold))

	destroy_lttng_session_ok $SESSION_NAME
}

function test_ust_local_size_pid ()
{
	diag "Rotate uid local session every 2MiB"
	local size_threshold=$((2 * 1024 * 1024))

	diag "Test ust local with size-based session rotation per-pid"
	create_lttng_session_ok $SESSION_NAME $TRACE_PATH
	enable_ust_lttng_channel_ok $SESSION_NAME "channel0" --buffers-pid
	enable_ust_lttng_event_ok $SESSION_NAME $EVENT_NAME "channel0"
	lttng_enable_rotation_size_ok $SESSION_NAME $size_threshold
	start_lttng_tracing_ok $SESSION_NAME

	# Cutoff at 100 times the expected size
	trace_until_n_archives produce_n_events "$TRACE_PATH" 3 $((3 * 100 * size_threshold))

	destroy_lttng_session_ok $SESSION_NAME
}

plan_tests $NUM_TESTS

print_test_banner "$TEST_DESC"
bail_out_if_no_babeltrace

start_lttng_relayd "-o $TRACE_PATH"
start_lttng_sessiond

tests=( test_ust_streaming_uid test_ust_local_uid \
	test_ust_streaming_pid test_ust_local_pid \
	test_ust_local_timer_uid test_ust_streaming_timer_uid \
	test_ust_local_timer_pid test_ust_streaming_timer_pid \
	test_ust_local_size_uid test_ust_local_size_pid \
	test_incompatible_sessions )

for fct_test in ${tests[@]};
do
	SESSION_NAME=$(randstring 16 0)
	${fct_test}
	clean_path $TRACE_PATH
done

stop_lttng_sessiond
stop_lttng_relayd

# Remove tmp dir
rm -rf $TRACE_PATH
